#ifndef _NE_MODEL_H_
#define _NE_MODEL_H_

#include <mathematics/AABB.h>
#include <mathematics/Node.h>
#include <graphics/Skin.h>
#include <graphics/Skeleton.h>
#include <animation/AnimationGroup.h>

namespace ne
{
    class NEAPI Model : public Node, public Resource
    {
        public:
            struct AnimationRTD
            {
                uint32_t mLayer;
                AnimationPtr mpAnimation;
                bool mRestart;
                bool mLoop;
                real mTime;
                real mTransTime;        // TODO: Need to fix
                real mWeight;
                real mScale;
                uint32_t mUserData;
            };

            typedef std::map<uint32_t, AnimationRTD*> AnimationRTDMap;
            typedef AnimationRTDMap::iterator AnimationRTDMapIterator;
            typedef AnimationRTDMap::const_iterator AnimationRTDMapConstIterator;

        public:
            Model(ResourceManager *pManager,
                  const uint64_t id,
                  const std::string &name,
                  const std::string &fileName,
                  const bool isManual,
                  ManualResourceLoader *pLoader);

            virtual Skin* createSkin(const std::string &name,
                                     const MeshPtr &pMesh);

            virtual Skin* getSkin(const std::string &name) const;

            virtual bool hasSkin(const std::string &name) const;

            virtual size_t getSkinCount() const;

            virtual bool destroySkin(const std::string &name);

            virtual void destroyAllSkins();

            virtual const SkinMap& getSkinMap() const;

            // TODO: This function copy the pSkeleteon data
            //       mpSkeleton and pSkeleton are not the same reference
            virtual void setSkeleton(const SkeletonPtr &pSkeleton);

            virtual SkeletonPtr getSkeleton() const;

            virtual void setAnimationGroup(const AnimationGroupPtr &pAnimationGroup);

            virtual AnimationGroupPtr getAnimationGroup() const;

            virtual void setSpeed(const real speed);
            virtual real getSpeed() const;

            virtual const AABB& getAABB() const;

            // TODO: If play animation
            //       The animation with the same layer in request map will be removed
            virtual void playAnimation(const std::string &name,
                                       const uint32_t layer=0,
                                       const bool restart=true,
                                       const bool loop=false,
                                       const real transTime=0.0f,
                                       const real weight=1.0f,
                                       const real scale=1.0f,
                                       const uint32_t userData=0);

            virtual void requestAnimation(const std::string &name,
                                          const uint32_t layer=0,
                                          const bool loop=false,
                                          const real transTime=0.0f,
                                          const real weight=1.0f,
                                          const real scale=1.0f,
                                          const uint32_t userData=0);

            virtual void pauseAnimation();

            virtual void continueAnimation();

            virtual void stopAnimation(const uint32_t layer=0);

            virtual void stopAllAnimation();

            virtual const AnimationRTD* getCurrentAnimationRTD(const uint32_t layer=0) const;

            virtual AnimationPtr getCurrentAnimation(const uint32_t layer=0) const;

            virtual uint32_t getCurrentAnimationUserData(const uint32_t layer=0) const;

            virtual bool isPlayingAnimation() const;

            virtual bool isPausingAnimation() const;

            virtual bool tick(const real interval);

            virtual void update();

            virtual void needUpdate(const bool forceParentUpdate=false);

            virtual void needUpdateAABB();

            virtual void updateAABB();

            virtual ~Model();

        protected:
            virtual bool loadImplement();

            virtual bool unloadImplement();

            virtual bool  downloadImplement();

            virtual size_t _calculateSize() const;

            virtual void _updateAABB() const;

        protected:
            SkinMap mSkinMap;
            SkeletonPtr mpSkeleton;
            AnimationGroupPtr mpAnimationGroup;
            real mSpeed;
            bool mPausingAnimation;
            mutable AABB mAABB;
            mutable bool mNeedUpdateAABB;

            AnimationRTDMap mCurrentAnimationMap;
            AnimationRTDMap mRequestAnimationMap;
    };

    class NEAPI ModelPtr : public SharedPtr<Model>
    {
        public:
            ModelPtr() : SharedPtr<Model>() {}
            explicit ModelPtr(Model *pModel) : SharedPtr<Model>(pModel) {}
            ModelPtr(const ModelPtr &pModel) : SharedPtr<Model>(pModel) {}

            ModelPtr(const ResourcePtr &p)
                : SharedPtr<Model>()
            {
                mpPtr = static_cast<Model*>(p.ptr());
                mpUseCount = p.getUseCountPtr();
                if (mpUseCount)
                {
                    ++(*mpUseCount);
                }
            }

            ModelPtr& operator = (const ResourcePtr &p)
            {
                if (mpPtr == static_cast<Model*>(p.ptr()))
                {
                    return *this;
                }
                ModelPtr temp(p);
                swap(temp);
                return *this;
            }
    };
}
#endif

